// exception.cc
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "../threads/system.h"
#include "syscall.h"
#include <stdio.h>
#include <iostream>
#include "../threads/thread.h"
#include "synch.h"
#include "indexedList.h"

//extern struct processTable ProcessTable;

using namespace std;

IndexList *LockList = NULL;
IndexList *ConditionList = NULL;

int g_ProcessId = 1;

int copyin(unsigned int vaddr, int len, char *buf) {
	// Copy len bytes from the current thread's virtual address vaddr.
	// Return the number of bytes so read, or -1 if an error occors.
	// Errors can generally mean a bad virtual address was passed in.
	bool result;
	int n = 0; // The number of bytes copied in
	int *paddr = new int;

	while (n >= 0 && n < len) {
		result = machine->ReadMem(vaddr, 1, paddr);
		while (!result) // FALL 09 CHANGES
		{
			result = machine->ReadMem(vaddr, 1, paddr); // FALL 09 CHANGES: TO HANDLE PAGE FAULT IN THE ReadMem SYS CALL
		}

		buf[n++] = *paddr;

		if (!result) {
			//translation failed
			return -1;
		}

		vaddr++;
	}

	delete paddr;
	return len;
}

int copyout(unsigned int vaddr, int len, char *buf) {
	// Copy len bytes to the current thread's virtual address vaddr.
	// Return the number of bytes so written, or -1 if an error
	// occors.  Errors can generally mean a bad virtual address was
	// passed in.
	bool result;
	int n = 0; // The number of bytes copied in

	while (n >= 0 && n < len) {
		// Note that we check every byte's address
		result = machine->WriteMem(vaddr, 1, (int) (buf[n++]));

		if (!result) {
			//translation failed
			return -1;
		}

		vaddr++;
	}

	return n;
}

void Create_Syscall(unsigned int vaddr, int len) {
	// Create the file with the name in the user buffer pointed to by
	// vaddr.  The file name is at most MAXFILENAME chars long.  No
	// way to return errors, though...
	char *buf = new char[len + 1]; // Kernel buffer to put the name in

	if (!buf)
		return;

	if (copyin(vaddr, len, buf) == -1) {
		printf("%s", "Bad pointer passed to Create\n");
		delete buf;
		return;
	}

	buf[len] = '\0';

	fileSystem->Create(buf, 0);
	delete[] buf;
	return;
}

int Open_Syscall(unsigned int vaddr, int len) {
	// Open the file with the name in the user buffer pointed to by
	// vaddr.  The file name is at most MAXFILENAME chars long.  If
	// the file is opened successfully, it is put in the address
	// space's file table and an id returned that can find the file
	// later.  If there are any errors, -1 is returned.
	char *buf = new char[len + 1]; // Kernel buffer to put the name in
	OpenFile *f; // The new open file
	int id; // The openfile id

	if (!buf) {
		printf("%s", "Can't allocate kernel buffer in Open\n");
		return -1;
	}

	if (copyin(vaddr, len, buf) == -1) {
		printf("%s", "Bad pointer passed to Open\n");
		delete[] buf;
		return -1;
	}

	buf[len] = '\0';

	f = fileSystem->Open(buf);
	delete[] buf;

	if (f) {
		if ((id = currentThread->space->fileTable.Put(f)) == -1)
			delete f;
		return id;
	} else
		return -1;
}

void Write_Syscall(unsigned int vaddr, int len, int id) {
	// Write the buffer to the given disk file.  If ConsoleOutput is
	// the fileID, data goes to the synchronized console instead.  If
	// a Write arrives for the synchronized Console, and no such
	// console exists, create one. For disk files, the file is looked
	// up in the current address space's open file table and used as
	// the target of the write.

	char *buf; // Kernel buffer for output
	OpenFile *f; // Open file for output

	if (id == ConsoleInput)
		return;

	if (!(buf = new char[len])) {
		printf("%s", "Error allocating kernel buffer for write!\n");
		return;
	} else {
		if (copyin(vaddr, len, buf) == -1) {
			printf("%s", "Bad pointer passed to to write: data not written\n");
			delete[] buf;
			return;
		}
	}

	if (id == ConsoleOutput) {
		for (int ii = 0; ii < len; ii++) {
			printf("%c", buf[ii]);
		}

	} else {
		if ((f = (OpenFile *) currentThread->space->fileTable.Get(id))) {
			f->Write(buf, len);
		} else {
			printf("%s", "Bad OpenFileId passed to Write\n");
			len = -1;
		}
	}

	delete[] buf;
}

int Read_Syscall(unsigned int vaddr, int len, int id) {
	// Write the buffer to the given disk file.  If ConsoleOutput is
	// the fileID, data goes to the synchronized console instead.  If
	// a Write arrives for the synchronized Console, and no such
	// console exists, create one.    We reuse len as the number of bytes
	// read, which is an unnessecary savings of space.
	char *buf; // Kernel buffer for input
	OpenFile *f; // Open file for output

	if (id == ConsoleOutput)
		return -1;

	if (!(buf = new char[len])) {
		printf("%s", "Error allocating kernel buffer in Read\n");
		return -1;
	}

	if (id == ConsoleInput) {
		//Reading from the keyboard
		scanf("%s", buf);

		if (copyout(vaddr, len, buf) == -1) {
			printf("%s", "Bad pointer passed to Read: data not copied\n");
		}
	} else {
		if ((f = (OpenFile *) currentThread->space->fileTable.Get(id))) {
			len = f->Read(buf, len);
			if (len > 0) {
				//Read something from the file. Put into user's address space
				if (copyout(vaddr, len, buf) == -1) {
					printf("%s",
							"Bad pointer passed to Read: data not copied\n");
				}
			}
		} else {
			printf("%s", "Bad OpenFileId passed to Read\n");
			len = -1;
		}
	}

	delete[] buf;
	return len;
}

void Close_Syscall(int fd) {
	// Close the file associated with id fd.  No error reporting.
	OpenFile *f = (OpenFile *) currentThread->space->fileTable.Remove(fd);

	if (f) {
		delete f;
	} else {
		printf("%s", "Tried to close an unopen file\n");
	}
}
void exec_Thread(int vaddr) {
	//initialize registers, restore them, then run

	currentThread->space->InitRegisters();
	currentThread->space->RestoreState();
	machine->Run();
	ASSERT(FALSE);

}

SpaceId Exec_Syscall(int vaddr, int size) {

	Thread *newThread;
	OpenFile *executable;
	int myProcess;
	char *buf = new char[size + 1];
	AddrSpace *space;

	//Copy MAXFILENAME bytes from virtual address vaddr to buffer buf.
	//If translation fails then -1 is returned
	ASSERT(copyin(vaddr,size,buf) !=-1);

	executable = fileSystem->Open(buf);
	ASSERT(executable != NULL);

	//Creating new address space for executable
	space = new AddrSpace(executable);

	//create base thread for the process
	newThread = new Thread("newThread");

	//Allocate the space allocated to the new thread

	newThread->space = space;
	//int num = newThread->space->NewPages();


	//Acquire Lock for Process Table
	ProcessTableLock->Acquire();

	//Update the process Table
	ProcessTable.spaceID[g_ProcessId] = g_ProcessId;
	myProcess = ProcessTable.spaceID[g_ProcessId];
	ProcessTable.counterProcess++;
	//ProcessTable.counterThread++;
	ProcessTable.ProcessSpace[g_ProcessId] = space;
	ProcessTable.noOfThreads[g_ProcessId] = 1;
	g_ProcessId++;

	cout << "Process Number : " << myProcess << endl;

	ProcessTableLock->Release();

	//delete executable;
	//fork the new thread
	newThread->Fork((VoidFunctionPtr) exec_Thread, 0);

	//Return the new space ID

	return (myProcess);
}

void kernel_thread(int virtualAddress) {
	//   int* addrInfo = (int*)arg;
	currentThread->space->InitRegisters();
	// Expand the page table
	int newPageAddress = currentThread->space->NewPages();
	//       int virtualAddress = addrInfo[0];
	//       int newPageAddress = addrInfo[1];
	// Set StackReg to new place
	machine->WriteRegister(StackReg, (newPageAddress - 16));
	// write  to the register PCReg the virtual address.
	machine->WriteRegister(PCReg, virtualAddress);
	machine->WriteRegister(NextPCReg, virtualAddress + 4);
	currentThread->space->RestoreState();
	machine->Run();

}

void Fork_Syscall(int vaddr) {


	int myProcess;
	//Create a New Thread
	ForkLock->Acquire();
	Thread *ThreadToBeForked;
	ThreadToBeForked = new Thread("ThreadToBeForked");

	//      int newPageAddress = currentThread->space->NewPages();
	//      int* addrInfo = new int[2];
	//      addrInfo[0]=vaddr;
	//      addrInfo[1]=newPageAddress;


	//Allocate the Address Space
	ThreadToBeForked->space = currentThread->space;

	//Update process table

	//Acquire Lock for Process Table
	       ProcessTableLock->Acquire();
	      // ProcessTable.noOfThreads[g_ProcessId-1]++;
	       //cout<<"ProcessTable.noOfThreads[g_ProcessId-1]:"<<ProcessTable.noOfThreads[g_ProcessId-1]<<endl;
	       ProcessTable.counterThread++;
	       cout << "@@@@@Total number of threads : " << ProcessTable.counterThread << endl;
	//Search for which process the thread belongs to based on
	//space

	       for(int i=0;i<5;i++)
	       {

	           if(ProcessTable.ProcessSpace[i] == currentThread->space){
	               //Increment the count for number of threads
	               //cout<<"ProcessTable.ProcessSpace[i]:"<<ProcessTable.ProcessSpace[i]<<endl;
	               ProcessTable.noOfThreads[i]++;

	               cout << "#####Process No : " << i << "got thread " << ProcessTable.noOfThreads[i] << endl ;
	               //ProcessTable.counterThread++;
	           }

	       }

	       ProcessTableLock->Release();

	//Make it a kernel Thread
	ThreadToBeForked->Fork((VoidFunctionPtr) kernel_thread, vaddr);
	ForkLock->Release();
	//  currentThread->Yield();


}

void Yield_Syscall() {
	currentThread->Yield();
}

void Exit_Syscall(int status) {
	int ProcessId;


		//get the process
		for(int i=0; i < 5 ; i++)
		{
			 if(ProcessTable.ProcessSpace[i] == currentThread->space)
			 {
				 ProcessId = i;

			 }
		}

	//Last Process + Last Thread  --  halt the nachos
	if(ProcessTable.counterProcess == 1 && ProcessTable.noOfThreads[ProcessId] == 1 )
	{		cout << "Last Process + Last Thread : " << ProcessId;

		currentThread->Finish();
		interrupt->Halt();

	}//If this thread is the last thread of any given process.
	else if(ProcessTable.noOfThreads[ProcessId] == 1)
	{
		ProcessTable.noOfThreads[ProcessId] = 0;
		//Decrement the number of process in the process table
		ProcessTable.counterProcess--;
		ProcessTable.counterThread--;
		//release the memory
		ProcessTable.ProcessSpace[ProcessId] = NULL;

		cout << "Last thread of the process :" << ProcessId <<endl;
		//finish the current thread
		currentThread->Finish();

	}else if(ProcessTable.noOfThreads[ProcessId] > 0){
		ProcessTable.noOfThreads[ProcessId]--;
	    ProcessTable.counterThread--;
	    cout << " Decrement one thread of the process : " << ProcessId << endl;
		currentThread->Finish();
	}else
	{
		cout << "Finish" << endl;
		currentThread->Finish();
	}





}

int getLength(unsigned int ptr) {
	char *charPtr = new char;
	int i = 0;
	//printf("\nIn getLength!!");
	while (1) {
		copyin(ptr, 1, charPtr);
		if (*charPtr == '\0')
			break;
		else {
			i++;
			ptr++;
		}
	}
	//printf("\n**-> String length: %d", i);
	return i;
}

int CreateLock_Syscall(unsigned int idAddr) {

	Lock *newLock;
	char *id;
	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	printf("Lock Name: %s\n", id);
	// If the linked list
	if (LockList == NULL)
		LockList = new IndexList();
	//if the lock does not exists create a new one
	if (LockList->getReference(id) == NULL) {
		newLock = new Lock(id);
		LockList->Append((void*) newLock, id);
		printf("%s: Lock Created !!\n", id);
		return 1;
	}
	//else do not create a duplicate one - return proper error message
	printf("%s: Lock Already Exists !!\n", id);
	return 0;
}

int DestroyLock_Syscall(unsigned int idAddr) {
	char *id;
	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	printf("Lock Name: %s\n", id);

	//If there are no locks in the list of locks
	if (LockList == NULL) {
		printf("No Locks available - List Empty\n");
		return 2;
	}
	//If the lock does not exisit in the list
	if (LockList->getReference(id) == NULL) {
		printf("%s: Lock Doesn't Exist !!\n", id);
		return 0;
	}
	//Remove the lock from the list
	LockList->Remove(id);
	printf("%s: Lock Removed !!\n", id);
	return 1;
}

int Acquire_Syscall(unsigned int idAddr) {
	Lock *newLock;
	int retValue;
	char *id;

	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	printf("Lock Name: %s\n", id);
	newLock = (Lock*) LockList->getReference(id);
	if (newLock != NULL) {
		retValue = newLock->Acquire();
		//printf("%s: Lock acquired!!\n", id);
		return retValue;
	}
	//printf("%s: Lock does not exist!!\n", id);
	return 0;
}

int Release_Syscall(unsigned int idAddr) {
	Lock *newLock;
	char *id;
	int retValue;

	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	printf("Lock Name: %s\n", id);
	newLock = (Lock*) LockList->getReference(id);
	if (newLock != NULL) {
		retValue = newLock->Release();
		//printf("%s: Lock released!!\n", id);
		return retValue;
	}
	printf("%s: Lock does not exist!!\n", id);
	return 0;
}

int CreateCondition_Syscall(unsigned int idAddr) {

	Condition *newCV;
	char *id;

	//read the character string from memeory location
	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	printf("Condition Var Name: %s\n", id);
	// If the linked list doesnt exist
	if (ConditionList == NULL)
		ConditionList = new IndexList();
	//check if CV exists
	if (ConditionList->getReference(id) == NULL) {
		newCV = new Condition(id);
		ConditionList->Append((void*) newCV, id);
		printf("%s: Condition Var Created !!\n", id);
		return 1;
	}
	printf("%s: Condition Var Already Exists !!\n", id);
	return 0;
}

int DestroyCondition_Syscall(unsigned int idAddr) {
	char *id;
	//copying the string
	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	printf("CV Name: %s\n", id);

	if (ConditionList == NULL) {
		printf("No Condition Var available - List Empty\n");
		return 2;
	}
	if (ConditionList->getReference(id) == NULL) {
		printf("%s: Condition Var Doesn't Exist !!\n", id);
		return 0;
	}
	ConditionList->Remove(id);
	printf("%s: Condition Var Removed !!\n", id);
	return 1;
}

int Signal_Syscall(unsigned int idAddr, unsigned int lockAddr) {
	Condition *newCV;
	char *id;
	char *lockName;
	int retValue;

	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	size = getLength(lockAddr);

	lockName = new char[size + 1];
	copyin(lockAddr, size, lockName);
	lockName[size] = '\0';

	printf("CV Name: %s\n", id);
	newCV = (Condition*) ConditionList->getReference(id);
	if (newCV != NULL) {
		retValue = newCV->Signal((Lock*) LockList->getReference(lockName));
		//printf("%s: Signalling!!\n", id);
		return retValue;
	}
	printf("%s: CV does not exist!!\n", id);
	return 0;
}

int Wait_Syscall(unsigned int idAddr, unsigned int lockAddr) {
	Condition *newCV;
	char *id;
	char *lockName;
	int retValue;
	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	size = getLength(lockAddr);

	lockName = new char[size + 1];
	copyin(lockAddr, size, lockName);
	lockName[size] = '\0';

	printf("CV Name: %s\n", id);
	newCV = (Condition*) ConditionList->getReference(id);
	if (newCV != NULL) {
		retValue = newCV->Wait((Lock*) LockList->getReference(lockName));
		//printf("%s: in wait!!\n", id);
		return retValue;
	}
	printf("%s: CV does not exist!!\n", id);
	return 0;
}

int Broadcast_Syscall(unsigned int idAddr, unsigned int lockAddr) {
	Condition *newCV;
	char *id;
	char *lockName;

	int size = getLength(idAddr);

	id = new char[size + 1];
	copyin(idAddr, size, id);
	id[size] = '\0';

	size = getLength(lockAddr);

	lockName = new char[size + 1];
	copyin(lockAddr, size, lockName);
	lockName[size] = '\0';

	printf("CV Name: %s\n", id);
	newCV = (Condition*) ConditionList->getReference(id);
	if (newCV != NULL) {
		int retValue = newCV->Broadcast(
				(Lock*) LockList->getReference(lockName));
		//printf("%s: Broadcast!!\n", id);
		return retValue;;
	}
	printf("%s: CV does not exist!!\n", id);
	return 0;
}

void ExceptionHandler(ExceptionType which) {
	int type = machine->ReadRegister(2); // Which syscall?
	int rv = 0; // the return value from a syscall

	if (which == SyscallException) {
		switch (type) {
		default:
			DEBUG('a', "Unknown syscall - shutting down.\n");
		case SC_Halt:
			DEBUG('a', "Shutdown, initiated by user program.\n");
			interrupt->Halt();
			break;
		case SC_Create:
			DEBUG('a', "Create syscall.\n");
			Create_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
			break;
		case SC_Open:
			DEBUG('a', "Open syscall.\n");
			rv = Open_Syscall(machine->ReadRegister(4),
					machine->ReadRegister(5));
			break;
		case SC_Write:
			DEBUG('a', "Write syscall.\n");
			Write_Syscall(machine->ReadRegister(4), machine->ReadRegister(5),
					machine->ReadRegister(6));
			break;
		case SC_Read:
			DEBUG('a', "Read syscall.\n");
			rv = Read_Syscall(machine->ReadRegister(4),
					machine->ReadRegister(5), machine->ReadRegister(6));
			break;
		case SC_Close:
			DEBUG('a', "Close syscall.\n");
			Close_Syscall(machine->ReadRegister(4));
			break;
		case SC_Exec:
			DEBUG('a', "Exec syscall.\n");
			rv = Exec_Syscall(machine->ReadRegister(4),
					machine->ReadRegister(5));
			break;
		case SC_Fork:
			DEBUG('a', "Fork syscall.\n");
			Fork_Syscall(machine->ReadRegister(4));
			break;
		case SC_Yield:
			DEBUG('a', "Yield syscall.\n");
			Yield_Syscall();
			break;
		case SC_Exit:
			DEBUG('a', "Exit syscall. \n");
			//currentThread->Finish();
			Exit_Syscall(machine->ReadRegister(4));
			break;
		case SC_CreateLock:
			DEBUG('a', "CreateLock syscall.\n");
			//printf("\nIn Case\n");
			rv = CreateLock_Syscall(machine->ReadRegister(4));
			break;
		case SC_DestroyLock:
			DEBUG('a', "DestroyLock Syscall.\n");
			rv = DestroyLock_Syscall(machine->ReadRegister(4));
			break;
		case SC_CreateCondition:
			DEBUG('a', "CreateCondition syscall.\n");
			rv = CreateCondition_Syscall(machine->ReadRegister(4));
			break;
		case SC_DestroyCondition:
			DEBUG('a', "DestroyCondition Syscall.\n");
			rv = DestroyCondition_Syscall(machine->ReadRegister(4));
			break;
		case SC_Acquire:
			DEBUG('a', "Acquire Syscall.\n");
			rv = Acquire_Syscall(machine->ReadRegister(4));
			break;
		case SC_Release:
			DEBUG('a', "Release Syscall.\n");
			rv = Release_Syscall(machine->ReadRegister(4));
			break;
		case SC_Signal:
			DEBUG('a', "Signal Syscall.\n");
			rv = Signal_Syscall(machine->ReadRegister(4),
					machine->ReadRegister(5));
			break;
		case SC_Wait:
			DEBUG('a', "Wait Syscall.\n");
			rv = Wait_Syscall(machine->ReadRegister(4),
					machine->ReadRegister(5));
			break;
		case SC_Broadcast:
			DEBUG('a', "Boradcast Syscall.\n");
			rv = Broadcast_Syscall(machine->ReadRegister(4),
					machine->ReadRegister(5));
			break;
		}

		// Put in the return value and increment the PC
		machine->WriteRegister(2, rv);
		machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
		machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
		machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg) + 4);
		return;
	} else {
		cout << "Unexpected user mode exception - which:" << which << "  type:"
				<< type << endl;
		interrupt->Halt();
	}
}
